/*
	Plik nagłówkowy programu virtual.
*/

#include "link.h"
#include <pthread.h>
#include "link_virtual.h"
#include <unistd.h>
#define ITERATION_INTERVAL 100000

Link *linksim = NULL;
pthread_mutex_t link_mutex = PTHREAD_MUTEX_INITIALIZER;

unsigned long Reflect(unsigned long ref, char ch)
{
	unsigned long value = 0;
	int i;

	for(i = 1; i < (ch + 1); ++i)
	{
		if (ref & 1)
			value |= 1 << (ch - i);
		ref >>= 1;
	}

	return value; 
}

void initCRC32()
{
	unsigned long ulPolynomial = 0x04c11db7;
	int i, j;
	for(i = 0; i <= 0xFF; i++)
	{
		crc32_table[i] = Reflect(i, 8) << 24;
		for (j = 0; j < 8; j++)
			crc32_table[i] = (crc32_table[i] << 1) ^ (crc32_table[i] & (1 << 31) ? ulPolynomial : 0);
		crc32_table[i] = Reflect(crc32_table[i], 32);
	} 
}

char bin2char(int a)
{
	if (a > -1 && a < 10) return a + 48;
	else return a + 87;
}

char ** initMatrix(struct Urzadzenie * dev)
{
	char buffer[10];
	char ** matrix;
	FILE * file = fopen("matrix.dat", "r");
	if (file == NULL)
	{
		fprintf(stderr, "virtual: brak macierzy widzialnosci\n");
		return NULL;
	}
	
    while( fread( buffer, 6, 1, file ) == 1 )
    {
        printf( "%x %x : %x %x\n", *((uint*)buffer), *((uint*)buffer+1), *((uint*)dev->adres_mac), *((uint*)dev->adres_mac+1));
        if( bcmp( buffer, dev->adres_mac, 6 ) == 0 )
        {
            if( fread( buffer, 1, 1, file ) != 1 )
            {
                fclose( file );
                return NULL;
            }
            matrix = (char **) malloc( buffer[0]+1 * sizeof( char* ) );

            uint i;
            for( i = 0; i < buffer[0]; i++ )
            {
                matrix[i] = (char *) malloc( 6 );
                if( fread( matrix[i], 6, 1, file ) != 1 )
                {
                    fclose( file );
                    return NULL;
                }
            }
            matrix[i] = NULL;
            return matrix;
        }
        else
        {
            fread( buffer, 1, 1, file );
            fread( buffer, 6, buffer[0], file );
        }
    }

	fprintf(stderr, "virtual: brak adresu mac w macierzy\n");
    fclose( file );
	return NULL;
}

void* extractField(char *ramka, int offset, int length)
{
	char bajt;
	char k;
	int wynik;
	char * field = (char*)malloc(length*sizeof(char));

	for (k=0; k < length; ++k)
	{
		bajt = ramka[offset+k];

		wynik = bajt & 15;
		field[2*k+1]=bin2char(wynik);

		wynik = (bajt >> 4) & 15;
		field[2*k]=bin2char(wynik);
	}

	return field;
}

int dodajSSID(char *ramka, int dlugosc_ramki, char ssid){
	// wstawianie na koncu ramki	
	memcpy(&ramka[dlugosc_ramki], &ssid, 1);	// wstawia ssid
	return dlugosc_ramki+1;
}
int dodajDlugosc(char *ramka, int dlugosc_ramki){
	unsigned int length;
	length = (ramka[12] << 8) + ramka[13];
	
	if (length <= MTU){ // dlugosc mniejsza niz 1500, pole dlugosci

		length += SSID;
		while (length < 46){ // wypelnianie ramki, 1B - SSID
			ramka[dlugosc_ramki++] = 0;
			++length;
		}
		
		ramka[12] = length / 256;
		ramka[13] = length % 256;

		return dlugosc_ramki;

	} else if (length >= 1536) { // w przeciwnym wypadku typ
		memcpy(&ramka[dlugosc_ramki], &ramka[12], 2);	// kopiuje pole typu
		dlugosc_ramki += 2;
		ramka[12] = (dlugosc_ramki - 12) / 256; // dlugosc pola ladunku pomniejszona o 12bajtow adresow
		ramka[13] = (dlugosc_ramki - 12) % 256;
		
		length = dlugosc_ramki - 12 + SSID;
		while (length < 46){ // wypelnianie ramki, 1B - SSID
			ramka[dlugosc_ramki++] = 0;
			++length;
		}
		
		ramka[12] = length / 256;
		ramka[13] = length % 256;


		return dlugosc_ramki + 2;
	}
    else
        return -1; /* Nie powinno nastąpić */
	
}
int dodajCRC32(char *ramka, int dlugosc_ramki){
	unsigned int crc32;
	crc32 = CRC32(ramka, dlugosc_ramki);
	printf("\nCRC32: %4x\n", crc32);
	
	
	memcpy(&ramka[dlugosc_ramki], &crc32, sizeof(crc32));

	memcpy(&crc32, &ramka[dlugosc_ramki], sizeof(crc32));
	printf("\nCRC32: %4x\n", crc32);

	return dlugosc_ramki+4;
}


/* 
	Tworzenie urządzenia oraz odpowiadającego mu interfejsu.
	Jako argument przekazywana jest nazwa urządzenia do stworzenia.
*/
int stworzInterfejs(int num, int tapno)
{
	// stworzenie struktury reprezentującej interfejs, używany do konfiguracji urządzenia sieciowego
	// opis struktury ifreq -> man netdevice
	struct ifreq interfejs; 
	// rezultat wywoływanych funkcji
	int wynik; 
	// identyfikator pliku wykorzystywanych w operacjach wejścia/wyjścia
	int deskryptor_pliku; 
	// ścieżka do pliku, za pomocą którego odbywać się będzie komunikacja z urządzeniem
	char plik[20]; 
	// polecenie do uruchamienia skryptu tworzącego trwały interfejs
	char polecenie[20];
	char * nazwa_urzadzenia;
	nazwa_urzadzenia = urzadzenia[num].nazwa_urzadzenia;

	// tworzymy trwaly interfejs, ustawiamy IP, MAC oraz MTU
	sprintf(polecenie, "./virtualinit %d %d", tapno, MTU);
	system(polecenie);

	sprintf(plik, "/dev/net/%s", nazwa_urzadzenia);
	// otwarcie pliku w trybie odczytu/zapisu
	if((wynik = open(plik, O_RDWR)) < 0) 
	{
		fprintf(stderr, "virtual: nie udało się otworzyć %s.\n", plik);
		return wynik;
	}
	printf("virtual: otwarto %s.\n", plik);
	// jeśli operacja się powiodła rezultatem jest identyfikator pliku
	deskryptor_pliku = wynik;

	// tworzenie interfejsu
	memset(&interfejs, 0, sizeof(struct ifreq));
	// typ interfejsu - TAP
	// flaga IFF_NO_PI informuje jądro, aby nie dostarczać informacji o pakiecie - 4 dodatkowych bajtów na początku pakietu
	interfejs.ifr_flags = IFF_TAP | IFF_NO_PI;
	// nazwa interfejsu
	strncpy(interfejs.ifr_name, nazwa_urzadzenia, IFNAMSIZ); 
	// próba stworzenia urządzenia
	// ioctl - input/output control - funkcja przeznaczona do kontrolowania różnych urządzeń
	// argument TUNSETIFF pozwala systemowi na stworzenie interfejsu o innej nazwie, jeśli podana jest już używana
	if((wynik = ioctl(deskryptor_pliku, TUNSETIFF, &interfejs)) < 0) 
	{
		fprintf(stderr, "virtual: nie udało się utworzyć interfejsu %s\n", interfejs.ifr_name);
		close(deskryptor_pliku);
		return wynik;
	}

	// jesli uda mu sie wyciagnac mac, to przepisuje go do struktury
	if (0 == ioctl(deskryptor_pliku, SIOCGIFHWADDR, &interfejs)) 
	{
        memcpy( urzadzenia[num].adres_mac, interfejs.ifr_addr.sa_data, 6 );
	   
		puts("\nvirtual: Przypisano adres MAC urządzenia -> ");

		printf("%x %x\n", *((uint*)urzadzenia[num].adres_mac), *((uint*)urzadzenia[num].adres_mac+1));
	}
		
	fprintf(stderr, "virtual: utworzono interfejs %s (rządano %s).\n", interfejs.ifr_name, nazwa_urzadzenia);
	
	// funkcja zwraca dwie wartości - deskryptor pliku poprzez return oraz nazwę utworznego interfejsu poprzez referencje
	strcpy(urzadzenia[num].nazwa_urzadzenia, interfejs.ifr_name);
	urzadzenia[num].deskryptor_pliku = deskryptor_pliku;
	return 0;
}

/* 
	Odczytywanie danych przychodzacych z jądra do urzadzenia opisanego w tablicach pod indeksem numer_urzadzenia
*/
void *odbierzDane(void *dn)
{
    int numer_urzadzenia = (int) dn;
	// bufor, do którego zapisane zostaną odczytane dane
	// rozmiar bufora nie powinien być mniejszy od rozmiaru MTU (domyślnie MTU jest ustawione na 1500)
	char bufor[2000];

	// flaga, czy w zasiegu widzialnosci
	int success;
	// ilość odczytanych znaków
	int ilosc_znakow;
	// licznik pętli
	int i;
	char * adres_mac;
#if 0
	char ** matrix = initMatrix(&urzadzenia[numer_urzadzenia]);
	
	if (matrix == NULL)
	{
		pthread_exit( NULL );
	}
#endif

	while(1)
	{
		// odczytanie danych z pliku
		ilosc_znakow = read(urzadzenia[numer_urzadzenia].deskryptor_pliku, bufor, sizeof(bufor));
		
		if (ilosc_znakow < 0)
		{
			fprintf(stderr, "virtual: czytanie danych przychodzących z jądra\n");
			pthread_exit( NULL );
		}

#ifdef VERBOSE
		// wypisanie odczytanych danych
		printf("\nvirtual: odczytano %d bajtów z %s\n", ilosc_znakow, urzadzenia[numer_urzadzenia].nazwa_urzadzenia);
#endif
		
		if (ilosc_znakow > 0){
			adres_mac = bufor;
			
#ifdef VERBOSE
			printf("Adres MAC: ");
			printf("%x %x\n", *((uint*)adres_mac), *((uint*)adres_mac+1) & 0xffff);
#endif

			// przejrzyj liste sasiadow
			i = 0;
			success = 0;
#if 0
			while (matrix[i] != NULL)
			{
	            printf( "%x %x\n", *((uint*)bufor), *((uint*)bufor+1)&0xffff );
				if (0 == bcmp(adres_mac,matrix[i], 6)){
					success = 1;
#endif
#ifdef VERBOSE
					printf("\nvirtual : Stacja w zasiegu\n");
				
					printf("\nRamka przed dodaniem SSID: \n");
					for (int j=0; j < ilosc_znakow; ++j)
						printf("%02x ", bufor[j]);
#endif
					// ramka, dl_ramki, nr ssid

					ilosc_znakow = dodajSSID(bufor, ilosc_znakow, 5);
					ilosc_znakow = dodajDlugosc(bufor,ilosc_znakow);
                    if( ilosc_znakow < 0 )
                    {
                        fprintf( stderr, "Niepoprawna dlugosc ramki.\n" );
                        break;
                    }

#ifdef VERBOSE
					printf("\nRamka po dodaniu SSID: \n");
					for (int j=0; j < ilosc_znakow; ++j)
						printf("%02x", bufor[j]);
					printf("\n");
#endif
                    bool sent = false;
                    do
                    {
                        pthread_mutex_lock( &link_mutex );
                        if( linksim->frame_buffer[numer_urzadzenia].size == 0 )
                        {
                            linksim->frame_buffer[numer_urzadzenia].size = ilosc_znakow;
                            memcpy( linksim->frame_buffer[numer_urzadzenia].content,
                                    bufor, ilosc_znakow );
                            sent = true;
                        }
                        pthread_mutex_unlock( &link_mutex );
                    } while( !sent && usleep(ITERATION_INTERVAL) == 0 );
#if 0
				}
				++i;
			}
			if (!success) printf("virtual: stacja poza zasiegiem\n");
#endif
		}
	}
    pthread_exit( NULL );
}

/*
	Wysłanie danych z urządzenia opisanego w tablicach pod indeksem numer_urzadzenia do pozostalych wirtualnych kart sieciowych.
*/
void wyslijDoPozostalych(int numer_urzadzenia, char *bufor, int ilosc_znakow_do_wyslania)
{
	// ilość wysłanych znaków
   int ilosc_znakow_wyslanych;
	// licznik pętli
	int i;

	for (i = 0; i < ilosc_urzadzen; i++)
	{
		// nie przekazujemy danych do urządzenia, które je wysyła
		if (i == numer_urzadzenia)
			continue;

   	ilosc_znakow_wyslanych = write(urzadzenia[i].deskryptor_pliku, bufor, ilosc_znakow_do_wyslania);
   	printf("virtual: wysłano %d bajtów do %s\n", ilosc_znakow_wyslanych, urzadzenia[i].nazwa_urzadzenia);
	}
}
